/**
 * Assignment 2 - Pub crawl
 * Authors: Huy Nguyen (3430069)    James Gregory (3288801)
 */
#include <stdlib.h>
#include <stdio.h>
#include <mpi.h>
#include "pub.h"

#define PUB_VOTE 2001
#define WENCH_VOTE 2002

extern int delegate_sober, bodyguard_sober, num_delegates;
extern int my_id, my_delegate_group_id;
extern MPI_Group delegate_group;
extern MPI_Comm delegate_comm, world_comm;

int *votes;
int *bodyguard_secret_list_of_drunk_delegates;
int *delegate_list_of_drunk_delegates;
int *sent_backs;

static int *pub_list;
static int pub_list_size;

static void send_pub_vote(int *pub_vote) {
    pub_vote[0] = delegate_pub_vote();
    votes[my_delegate_group_id] = pub_vote[0];
    for(int id = 0; id < num_delegates; id++) {
        if(my_delegate_group_id != id) {
            if(delegate_sober < 0) {
                pub_vote[0] = bodyguard_pub_vote(id);
            }
            pub_vote[1] = bodyguard_vote_secret();
            MPI_Send(pub_vote, 2, MPI_INT, id, PUB_VOTE, delegate_comm); 
        }
    }
}

static void receive_pub_vote(int *pub_vote) {
    MPI_Status status;
    MPI_Recv(pub_vote, 2, MPI_INT, MPI_ANY_SOURCE, PUB_VOTE, delegate_comm, &status); 
    votes[status.MPI_SOURCE] = pub_vote[0];
    bodyguard_process_vote(pub_vote, status.MPI_SOURCE);
}

static int update_list(int pub_picked) {
    int next_location = pub_list[pub_picked];
    for(int i = pub_picked; i < pub_list_size - 1; i++) {
        pub_list[i] = pub_list[i+1];
    }
    pub_list_size--;
    return next_location;
}

static int vote_pub() {
    if (pub_list_size == 1) {
        return pub_list[0];
    }
    int pub_vote[2];
    for(int id = 0; id < num_delegates; id++) {
        if(id == my_delegate_group_id) {
            send_pub_vote(pub_vote);        
        } else {
            receive_pub_vote(pub_vote);
        }
    }
    return update_list(tally_votes(votes));
}

static void send_wench_vote(int *wench_nomination, int *wench_vote, int *wench_votes) {
    wench_vote[0] = delegate_wench_vote(wench_nomination);
    wench_votes[my_delegate_group_id] = wench_vote[0];
    for(int id = 0; id < num_delegates; id++) {
        if(my_delegate_group_id != id) {
            if(delegate_sober < 0) {
                wench_vote[0] = bodyguard_wench_vote(wench_votes[my_delegate_group_id], wench_nomination, id);
            }
            wench_vote[1] = bodyguard_vote_secret();
            MPI_Send(wench_vote, 2, MPI_INT, id, WENCH_VOTE, delegate_comm); 
        }
    }

}

static void receive_wench_vote(int *wench_vote, int *wench_votes) {
    MPI_Status status;
    MPI_Recv(wench_vote, 2, MPI_INT, MPI_ANY_SOURCE, WENCH_VOTE, delegate_comm, &status); 
    wench_votes[status.MPI_SOURCE] = wench_vote[0];
    bodyguard_process_vote(wench_vote, status.MPI_SOURCE);
}

static int vote_wench(int *wench_nominations) {
    int wench_vote[2];
    int wench_votes[num_delegates];
    for(int id = 0; id < num_delegates; id++) {
        if(id == my_delegate_group_id) {
            send_wench_vote(wench_nominations, wench_vote, wench_votes);        
        } else {
            receive_wench_vote(wench_vote, wench_votes);
        }
    }
    int wench_chosen = wench_nominations[tally_votes(wench_votes)];
    printf("delegate %d picked %d as wench.\n", my_id, wench_chosen);
    return wench_chosen;
}

static void nominate_wench(int chosen_pub, int *wench_nominations) {
    if(my_id == chosen_pub) {
        if(delegate_sober < 0) {
            bodyguard_nominate(wench_nominations);
        } else {
            delegate_nominate(wench_nominations);
        }
        MPI_Bcast(wench_nominations, 2, MPI_INT, my_delegate_group_id, delegate_comm);
    } else {
        MPI_Bcast(wench_nominations, 2, MPI_INT, chosen_pub-1, delegate_comm);
    }            
}

static int is_drunk(void) {
    return rand() % 2;
}

static void consume(int wench_id) {
    int sent_back = wench_id > BODYGUARD_ID_OFFSET ? 0 : 1;
    if (wench_id == my_id || wench_id == (my_id + BODYGUARD_ID_OFFSET)) {
        MPI_Gather(&sent_back, 1, MPI_INT, sent_backs, 1, MPI_INT, my_id - 1, delegate_comm);
        int *wench_sober = wench_id > BODYGUARD_ID_OFFSET ? &bodyguard_sober : &delegate_sober;
        for(int i = 0; i < num_delegates; i++) {
            if(!sent_backs[i]) {
                continue;
            }
            printf("wench %d consumes sent back drink from %d\n", wench_id, (i+1));
            if (*wench_sober < 0) { 
                break;  
            } else if(*wench_sober > 0) {
                (*wench_sober)--;
            } else {
                (*wench_sober)-= is_drunk();
            }
            if(*wench_sober < 0) {
                printf("wench %d is now drunk\n", wench_id);
            }
        }
    } else {
        if (delegate_sober == 0) {
            printf("delegate %d consumes beer from wench: %d\n", my_id, wench_id);
            delegate_sober-= is_drunk();
            if(delegate_sober < 0) {
                printf("delegate %d is now drunk\n", my_id);
            }
        } else if (delegate_sober > 0) {
            printf("delegate %d consuming beer from wench: %d\n", my_id, wench_id);
            delegate_sober--;
        } else {
            printf("delegate %d sends drink back\n", my_id);
            sent_back = 1;
        }
        int id = wench_id > BODYGUARD_ID_OFFSET ? wench_id - BODYGUARD_ID_OFFSET : wench_id;
        MPI_Gather(&sent_back, 1, MPI_INT, NULL, 1, MPI_INT, id - 1, delegate_comm);
   }
}

static void receive_pub_list(int num_delegates) {
    pub_list_size = num_delegates;
    int array[pub_list_size];
    pub_list = (int *)malloc(pub_list_size * sizeof(int));
    MPI_Bcast(&array, pub_list_size, MPI_INT, ROOT, world_comm);
    for (int i = 0; i < pub_list_size; i++) {
        pub_list[i] = array[i];
    }
}

void join_game(void) {
    receive_pub_list(num_delegates);
    int game_state = GAME_CONTINUE;
    int decision[3];
    int wench_nominations[2] = {0};
    votes = (int *)malloc(num_delegates * sizeof(int));
    sent_backs = (int *)malloc(num_delegates * sizeof(int));
    bodyguard_secret_list_of_drunk_delegates = (int *)malloc(num_delegates * sizeof(int));
    delegate_list_of_drunk_delegates = (int *)malloc(num_delegates * sizeof(int));
    for(int round = 0; round < num_delegates; round++) {
        MPI_Barrier(world_comm);

        decision[0] = vote_pub(); decision[1] = delegate_sober; decision[2] = bodyguard_sober;
        MPI_Gather(decision, 3, MPI_INT, NULL, 3, MPI_INT, ROOT, world_comm);
        
        printf("delegate %d turns up to pub %d.\n", my_id, decision[0]);
        MPI_Bcast(&game_state, 1, MPI_INT, ROOT, world_comm);
        if(game_state) { break; }
        
        MPI_Barrier(world_comm);
        
        nominate_wench(decision[0], wench_nominations);
        
        decision[0] = vote_wench(wench_nominations); decision[1] = delegate_sober; decision[2] = bodyguard_sober;
        MPI_Gather(decision, 3, MPI_INT, NULL, 3, MPI_INT, ROOT, world_comm);
        
        MPI_Bcast(&game_state, 1, MPI_INT, ROOT, world_comm);
        if(game_state) { break; }
        
        consume(decision[0]);
        MPI_Barrier(world_comm);
    }
    free(pub_list);
    free(sent_backs);
    free(votes);
}

